第24天。最近要離開一個熟悉地方,覺得感傷。
開放-封閉原則是被列在SOLID原則中,其定義是:軟體實體(類別、模組或是函數)應該是可以擴展的,但不可以修改。舉例子來說明,假設我們現在在執行大型專案,裡頭有非常多程式碼與檔案,如果今天有新需求要在window.onload中增加一些行為,比如說列印出所有畫面中元素總量,最簡單直接的做法當然就是直接編寫onload函數:
window.onload = function () {
console.log(document.getElementsByTagName('*').length);
};
通常這樣子的需求產生時,會直接搜尋相關程式碼,直接改寫他們。但這樣修改程式碼的行為多少帶些風險,可能容易引起其他的問題。假設onload內容已經是數百行的巨大函數,在不改動程式碼的狀況下有什麼方法可以滿足呢?我們可以利用之前練習的裝飾者模式來處理這狀況:
Function.prototype.after = function (fn) {
var _self = this;
return function () {
var ret = _self.apply(this, arguments);
fn.apply(this, arguments);
return ret;
}
};
window.onload = (window.onload || function () { }).after(
function () {
console.log(document.getElementsByTagName('*').length);
}
);
通過裝飾函數的方式,我們完全不用理會window.onload原本的行為。當需要改變或是新增一個程式功能的時候,可以使用增加程式的方式,但沒有改動到原本的程式碼,這樣就是開放-封閉原則。這樣的改法相較於直接改動程式碼更簡單也更安全。
在程式編寫中,過多的分支也是造成違反開放-封閉原則的一個常見原因。每當需求增加,就必須多一個else if,用switch意義上也是跟if else一樣的。實際舉個例子,現在我們要做一個各種動物叫聲的功能:
var makeSound = function (animal) {
if (animal instanceof Duck) {
console.log('呱呱');
} else if (animal instanceof Dog) {
console.log('汪汪');
}
};
var Duck = function () { };
var Dog = function () { };
makeSound(new Duck());
這時候假如我們要增加一個狐狸的叫聲,我們勢必就要修改makeSound函數,增加一個else if分支來寫。現在我們可以利用物件多態的角度來修改:
var makeSound = function (animal) {
animal.sound();
};
var Duck = function () { };
Duck.prototype.sound = function () {
console.log('呱呱');
};
var Dog = function () { };
Dog.prototype.sound = function () {
console.log('汪汪');
};
makeSound(new Duck());
這時候要增加狐狸叫聲就不用修改makeSound,直接加入物件:
var Fox = function () { };
Fox.prototype.sound = function () {
console.log('Ring-ding-ding-ding-dingeringeding');
}
這樣增加程式碼,而不是修改程式碼的方式,符合開放-封閉原則。